package dcutils.sock;

// Import Java JDK Classes
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

// Import Application Classes
import dcutils.Sequencer;

/**
 * A server
 * @author dca
 */
public class Server {
   public static final int DEFAULT_PORT = 15151;
   public static final List<String> DEFAULT_BREAK_STRINGS = Arrays.asList("quit", "exit", "QUIT", "EXIT");

   private ServerSocket ss;
   private Sequencer idSeq;
   private int maxConnects = Integer.MAX_VALUE;
   private List<Connection> connections = new ArrayList<Connection>();

   public Server() throws IOException {
      this(DEFAULT_PORT);
   } // END constructor

   public Server(int port) throws IOException {
      this.ss = new ServerSocket(port);
      this.idSeq = new Sequencer();
   } // END constructor

   public Server withMaxConnects(int maxConnects) {
      this.maxConnects = maxConnects;
      return this;
   } // END withMaxConnects

   public void startUp() throws IOException {
      System.out.printf("Listening for connections: max(%d)%n", maxConnects);
      final AsyncCmdLine cmdLine = new AsyncCmdLine(this.connections, DEFAULT_BREAK_STRINGS);

      while(cmdLine.isActive()) {
         // Wait for a connection
         final Socket socket = ss.accept();
         final int id = idSeq.nextValue();

         // Remove dead connections.
         removeDeadConnections();

         // Is there room for another connection?
         if(maxConnects > connections.size()) {
            // Add the connection to the list
            System.out.println("Adding socket as a connection.");
            final Connection c = new Connection(id, socket, DEFAULT_BREAK_STRINGS);
            c.send("YOUR_ID=" + id);
            connections.add(c);
            System.out.printf("Connection created with: id(%d), max(%d), curr(%d), avail(%d)%n", id, maxConnects, connections.size(), (maxConnects - connections.size()));
         } else {
            // Report server is full and close the socket
            System.out.println("Server is full.");
            reportServerFull(socket);
         } // END if/else
      } // END loop
   } // END startUp

   public void shutDown() throws IOException {
      ss.close();
   } // END shutDown

   public boolean isActive() {
      return !ss.isClosed() && ss.isBound();
   } // END isActive

   private void removeDeadConnections() {
      final Iterator<Connection> iter = connections.iterator();
      while(iter.hasNext()) {
         if(iter.next().isClosed()) iter.remove();
      } // END loop
   } // END removeDeadConnections

   private void reportServerFull(Socket socket) throws IOException {
      try(BufferedWriter socketOut = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
         socketOut.write("Server full.");
         socketOut.newLine();
         socketOut.write("Maximum connections reached.");
         socketOut.newLine();
         socketOut.flush();
      } catch(Exception exc) {
         System.out.printf("Warning: %s: Could not output message to client.  Message was: %s%n", exc.getClass().getSimpleName(), exc.getMessage());
         exc.printStackTrace();
      } finally {
         System.out.println("Closing socket.");
         socket.close();
      } // END try/catch/finally
   } // END reportServerFull

   public static void main(String[] args) {
      try {
         new Server().withMaxConnects(3).startUp();
      } catch(Exception exc) {
         System.out.printf("Warning: %s: Could not start server.  Message was: %s%n", exc.getClass().getSimpleName(), exc.getMessage());
         exc.printStackTrace();
      } // END try/catch
   } // END main
} // END class Server
